package fr.babos.navigation.routes 
{
	import com.asual.swfaddress.SWFAddress;
	import com.asual.swfaddress.SWFAddressEvent;
	import fr.babos.core.data.collect.DataCollection;
	import fr.babos.core.data.parse.DataParser;
	import fr.babos.core.data.remote.IDataRemote;
	import fr.babos.core.data.remote.RemotingCall;
	import fr.babos.core.data.remote.XMLRemote;
	import fr.babos.core.log.Log;
	import fr.babos.navigation.errors.NavigationError;
	import org.osflash.signals.DeluxeSignal;
	import org.osflash.signals.ISignal;
	
	/**
	 * @author ZoulouX
	 */
	public class Router implements IRouter
	{
		private var _lastURL:String;
		/**
		 * Lorsque l'URL change
		 */
		protected var _onURLChange						:DeluxeSignal						= new DeluxeSignal(this);
		
		/**
		 * Le signal de changement d'URL
		 */
		protected var _onRouteChange					:DeluxeSignal						= new DeluxeSignal(this);
		
		/**
		 * Le signal lorsqu'il y a une erreur sur le routage
		 */
		protected var _onRouteError						:DeluxeSignal						= new DeluxeSignal(this);
		
		/**
		 * Le signal lorsque les routes sont chargées
		 */
		protected var _onLoadSuccess					:DeluxeSignal						= new DeluxeSignal(this);
		
		/**
		 * Le signal lorsqu'il y a une erreur sur le routage
		 */
		protected var _onLoadError						:DeluxeSignal						= new DeluxeSignal(this);
		
		/**
		 * Récupérer la route courrante. Null si on est sur un 404
		 */
		protected var _currentRoute						:RouteItem;
		
		/**
		 * La liste des routes
		 */
		protected var _routes							:DataCollection						= new DataCollection();
		
		/**
		 * La remote pour les chargements
		 */
		protected var _remote							:IDataRemote;
		
		/**
		 * Les paramètres de la route en cours
		 */
		protected var _currentParams					:Object;
		
		/**
		 * Si le routeur démarre automatiquement après le chargement des routes
		 */
		protected var _autoStart						:Boolean;
		
		/**
		 * Si le routeur est démarré
		 */
		protected var _started							:Boolean
		
		
		/**
		 * Lorsque l'URL change
		 */
		public function get onURLChange ():ISignal { return _onURLChange; }
		
		/**
		 * Le signal de changement d'URL
		 */
		public function get onRouteChange ():ISignal { return _onRouteChange; }
		
		/**
		 * Le signal lorsqu'il y a une erreur sur le routage
		 */
		public function get onRouteError ():ISignal { return _onRouteError; }
		
		/**
		 * Le signal lorsque les routes sont chargées
		 */
		public function get onLoadSuccess ():ISignal { return _onLoadSuccess; }
		
		/**
		 * Le signal lorsqu'il y a une erreur sur le routage
		 */
		public function get onLoadError ():ISignal { return _onLoadError; }
		
		/**
		 * Récupérer la route courrante. Null si on est sur un 404
		 */
		public function get currentRoute ():RouteItem { return _currentRoute; }
		
		/**
		 * Les paramètres de la route en cours
		 */
		public function get currentParams():Object { return _currentParams; }
		
		/**
		 * La liste des routes
		 */
		public function get routes ():DataCollection { return _routes; }
		public function set routes (value:DataCollection):void 
		{
			_routes = value;
		}
		
		/**
		 * L'URL active sur le bootstrap
		 */
		public function get url ():String { return SWFAddress.getValue(); }
		public function set url (value:String):void
		{
			SWFAddress.setValue(value)
		}
		
		/**
		 * Si le routeur est démarré
		 */
		public function get started ():Boolean { return _started; }
		
		
		/**
		 * Le constructeur
		 */
		public function Router ()
		{
			
		}
		
		/**
		 * Charger la structure des routes
		 * @param	pURL : URL de l'objet XML/AMF/JSON contenant les routes
		 */
		public function loadRoutes (pURL:String, pRemote:IDataRemote = null, pAutoStart:Boolean = true):void 
		{
			// Remote XML par défaut
			if (pRemote == null)
			{
				// Remote XML par défaut
				_remote = new XMLRemote();
				
				// Ajouter le parseur et les types
				_remote.parser = new DataParser();
				_remote.parser.addItemType("route", RouteItem);
			}
			
			// Si ça démarre automatiquement
			_autoStart = pAutoStart;
			
			// Appeler 
			_remote.call(pURL, {
				onSuccess: loadRoutesSuccessHandler,
				onError: loadRoutesErrorHandler,
				collectionToFeed: _routes
			});
		}
		
		/**
		 * Les routes ont bien été chargées
		 */
		protected function loadRoutesSuccessHandler (pCall:RemotingCall):void
		{
			// Signaler que c'est prêt
			_onLoadSuccess.dispatch();
			
			// Démarrer automatiquement
			if (_autoStart)
				start();
		}
		
		/**
		 * Les routes n'ont pas été chargées correctement
		 */
		protected function loadRoutesErrorHandler (pCall:RemotingCall):void
		{
			Log.error("Error while loading routes.");
			
			// Erreur sur le chargement des routes
			_onLoadError.dispatch();
		}
		
		/**
		 * Activer l'écoute du SWFAddress
		 */
		public function start ():void
		{
			// Il est démarré
			_started = true;
			
			if (_lastURL == null)
				_lastURL = url;
			
			// Ecouter SWFAddress
			SWFAddress.addEventListener(SWFAddressEvent.CHANGE, URLChangeHandler);
			
			update();
		}
		
		/**
		 * Arrêter l'écoute du SWFAddress
		 */
		public function stop ():void
		{
			// Il est arrêté
			_started = false;
			
			// Ecouter SWFAddress
			SWFAddress.removeEventListener(SWFAddressEvent.CHANGE, URLChangeHandler);
		}
		
		/**
		 * Routage inverse, création d'une URL depuis une commande et ses paramètres nommés.
		 * Attention, cette méthode a la gachette facile sur le déclanchement d'erreur.
		 * En cas de commande non trouvée une erreur NavigationError sera déclanchée.
		 * @param	pCommand : Nom de la commande de base
		 * @param	pParams : Objet dynamique contenant les paramètres nommés
		 * @throws 	NavigationError en cas de route non trouvée
		 * @return 	L'URL générée
		 */
		public function makeURL (pCommand:String, pParams:Object = null):String 
		{
			// Parcourir les routes
			for each (var route:RouteItem in _routes.all)
			{
				// Si cette route répond à cette commande et a ces paramètres
				if (route.checkCommand(pCommand, pParams))
				{
					// Créer l'URL et retourner
					return route.getURLFromParams(pParams);
				}
			}
			
			// Déclancher une erreur
			throw new NavigationError("Router.makeURL", "Route not found for command " + pCommand + ".");
			
			// Ne rien retourner
			return "";
		}
		
		/**
		 * Routage inverse, ouvrir une commande. Cette action va changer l'URL selon la commande et les paramètres nommés.
		 * Attention, cette méthode a la gachette facile sur le déclanchement d'erreur.
		 * En cas de commande non trouvée une erreur NavigationError sera déclanchée.
		 * @param	pCommand : Nom de la commande de base
		 * @param	pParams : Objet dynamique contenant les paramètres nommés
		 * @throws 	NavigationError en cas de route non trouvée
		 */
		public function open (pCommand:String, pParams:Object = null):void 
		{
			url = makeURL(pCommand, pParams);
		}
		
		public function update ():void
		{
			if (_lastURL != url)
				URLChangeHandler()
		}
		
		/**
		 * L'adresse change
		 */
		protected function URLChangeHandler (event:SWFAddressEvent = null):void
		{
			_lastURL = url;
			
			// Mémoriser l'ancienne route
			var oldRoute:RouteItem = _currentRoute;
			
			_currentRoute = null;
			
			// Parcourir les routes
			for each (var route:RouteItem in _routes.all)
			{
				// Si cette route répond à cette URL
				if (route.checkURL(url))
				{
					// Séléctionner la route
					_currentRoute = route;
					
					// Récupérer les paramètres
					_currentParams = route.getParamsFromURL(url);
					
					// Arrêter de chercher
					break;
				}
			}
			
			// L'URL a changé
			_onURLChange.dispatch();
			
			// Si la route a changé
			if (oldRoute != _currentRoute)
			{
				// Dispatcher un changement
				_onRouteChange.dispatch();
			}
			
			// Si on a pas trouvé de route
			if (_currentRoute == null)
			{
				// Dispatcher un 404
				_onRouteError.dispatch();
				
				// Virer les paramètres
				_currentParams = {};
			}
		}
	}
}